////////////////////////////////////////////////////////////////////////////////
//
//	Original file Name: Sound.java
//	Originating Author: JWhitefield
//	
//	Description: Represents a single playable sound within the game 
//
////////////////////////////////////////////////////////////////////////////////

package com.jjw.game.sound;

import java.io.File;
import java.util.Map;

import javazoom.jlgui.basicplayer.BasicController;
import javazoom.jlgui.basicplayer.BasicPlayer;
import javazoom.jlgui.basicplayer.BasicPlayerEvent;
import javazoom.jlgui.basicplayer.BasicPlayerException;
import javazoom.jlgui.basicplayer.BasicPlayerListener;

/**
 * Represents a single playable sound within the game 
 * 
 * @author JWhitefield
 *
 */
public class Sound implements BasicPlayerListener{
	
	//--------------------------------------------------------------------------
	//
	//Properties
	//
	//--------------------------------------------------------------------------
		
	//----------------------------------
	// Public
	//----------------------------------
		
	/**
	 * Constants for sounds, these are not only the reference to the sounds but also the relative file path from the 
	 * sound assets folder 
	 */
	
	/**
	 * Background
	 */
	public static final String BACKGROUND_AMBIANT = "background/sound.mp3";
	public static final String BACKGROUND_PIANO = "background/piano.mp3";
	
	/**
	 * Effect
	 */
	public static final String EFFECT_SWORD_SWING = "effect/SwordSwing.mp3";
	
	//----------------------------------
	//  Getters / Setters
	//----------------------------------
	
	/**
	 * Is the sound playing
	 */
	boolean isPlaying() {
		
		//Check we have a sound player, if not return false
		if( this.soundPlayer == null)
			return false;
		
		//Return if we are playing or not
		return ( this.soundPlayer.getStatus() == BasicPlayer.PLAYING );
	}
	
	/**
	 * Is the sound paused
	 */
	boolean isPaused() {
		
		//Check we have a sound player, if not return false
		if( this.soundPlayer == null)
			return false;
		
		//Return if we are paused or not
		return ( this.soundPlayer.getStatus() == BasicPlayer.PAUSED );
	}

	/**
	 * Is the sound stopped
	 */
	boolean isStopped() {
		
		//Check we have a sound player, if not return false
		if( this.soundPlayer == null)
			return false;
		
		//Return if we are stopped or not
		return ( this.soundPlayer.getStatus() == BasicPlayer.STOPPED );
	}
		
	//----------------------------------
	// Private
	//----------------------------------
	
	/**
	 * Player which will play the sound
	 */
	private BasicPlayer soundPlayer;
	
	/**
	 * Is the track going to be looped once its finished
	 */
	private boolean loopTrack = false;
	
	//--------------------------------------------------------------------------
	//
	//  Methods
	//
	//--------------------------------------------------------------------------
	
	//----------------------------------
	// Public
	//----------------------------------
	
	Sound( String soundFilePath ) {
		
		//Call its own constructor with looping default off
		this(soundFilePath, false);
	}
	
	Sound( String soundFilePath, boolean loopSound) {

		//Set the loop property
		this.loopTrack = loopSound;
		
		//Load the sound file
		this.load(soundFilePath);
	}
	
	/**
	 * Plays the sound asset 
	 */
	void play() {
		
		//Check if we can perform this action 
		if( !this.canActionSound( BasicPlayer.PLAYING ) )
			return;
		
		//Enclose within a try just to check we can play it
		try {
					
			//Play the sound 
			this.soundPlayer.play();
			
			this.setVolume( SoundManager.DEFAULT_VOLUME );
			
		} catch (BasicPlayerException e) {
			
			//Print any exceptions
			e.printStackTrace();
		}
	}
	
	/**
	 * Pauses the sound asset
	 */
	void pause() {
		
		//Check if we can perform this action 
		if( !this.canActionSound( BasicPlayer.PAUSED ) )
			return;
		
		//Enclose within a try just to check we can pause it
		try {
			
			//Pause the sound 
			this.soundPlayer.pause();
			
		} catch (BasicPlayerException e) {
			
			//Print any exceptions
			e.printStackTrace();
		}
	}
	
	/**
	 * Resume the paused sound asset
	 */
	void resume() {
		
		//Check we have sound player if not stop here
		if( this.soundPlayer == null )
			return;
		
		//If the status of the sound isn't paused we cannot resume 
		if( this.soundPlayer.getStatus() != BasicPlayer.PAUSED )
			return;
		
		//Enclose within a try to check we can resume it 
		try {
			
			//Resume the sound
			this.soundPlayer.resume();
		} catch (BasicPlayerException e) {
		
			//Print any exceptions
			e.printStackTrace();
		}		
	}
	
	/**
	 * Stop the sound asset
	 */
	void stop() {
		
		//Check if we can perform this action 
		if( !this.canActionSound( BasicPlayer.STOPPED ) )
			return;
		
		//Enclose within a try to check we can stop it
		try {
			
			//Pause the sound 
			this.soundPlayer.stop();
		} catch (BasicPlayerException e) {
			
			//Print any exceptions
			e.printStackTrace();
		}
	}
	
	/**
	 * Sets the volume for the sound clip
	 */
	void setVolume( double volume ) {
		
		//If we havn't got a player we can't set the sound
		if( this.soundPlayer == null )
			return;
		
		BasicController bc = (BasicController) this.soundPlayer;
		
		//Normalises the volume between 0 and 1
		volume = Math.max(0, Math.min(volume, 1));
		
		//Enclose within a try
		try {
			
			//Set the volume
			bc.setGain(volume);
		} catch (BasicPlayerException e) {

			//Print any exceptions
			e.printStackTrace();
		}
	}
	
	/**
	 * Will attach a basic listener to this sound 
	 * 
	 * @param basicListener the object which want to be a listener to this sound object
	 */
	void attachObjectAsListener( BasicPlayerListener basicListener ) {
		
		//If the sound player is null we cannot attach the listener so return 
		if( this.soundPlayer == null )
			return;
		
		//Attach the object as a listener to the player
		this.soundPlayer.addBasicPlayerListener( basicListener );
	}
	
	/**
	 * Will remove a basic listener to this sound 
	 * 
	 * @param basicListener the object which want to be a removed as a listener to this sound object
	 */
	void removeObjectAsListener( BasicPlayerListener basicListener ) {
		
		//If the sound player is null we cannot remove is so return 
		if( this.soundPlayer == null )
			return;
		
		//Remove the object as a listener to the player
		this.soundPlayer.removeBasicPlayerListener( basicListener );
	}
	
	
	//----------------------------------
	// Private
	//----------------------------------
	
	/**
	 * Load the sound file passed in
	 * 
	 * @param filePath the file path of the sound asset we want to load
	 */
	private void load( String filePath ) {
		
		//If we already have a player then there is not point in loading the asset
		if( this.soundPlayer != null )
			return;
			
		//Create a relative file path for the sound asset based on the sound managers asset path and the path passed in 
		String relativeFilePath = SoundManager.ASSET_SOUND_DIRECTORY+filePath;
		
		//Enclose the asset loading within try catch
		try{
					
			//Create a file object for this sound file
			File soundFile = new File( relativeFilePath );
			
			//Create a new basic player 
			this.soundPlayer = new BasicPlayer();
			
			//Opens the asset into the sound player
			this.soundPlayer.open( soundFile.getAbsoluteFile() );
			
			//Set the pan to centre
			this.soundPlayer.setPan(0);
			
			//Set the default volume
			this.setVolume( SoundManager.DEFAULT_VOLUME );
			
			//Add the play back listener
			this.soundPlayer.addBasicPlayerListener(this);
		}catch (Exception e) {

			//Reset these back to null 
			this.soundPlayer = null;
			
			System.err.println("SOUND: "+ relativeFilePath + " failed to load");
		}
	}
	
	/**
	 * Checks for existence of a sound controller and player and also the action state we are to action 
	 * 
	 * @param actionState	the static sound player states found in the BasicPlayer
	 * 
	 * @return	weather we can action this sound or not 
	 */
	private boolean canActionSound( int actionState ) {
		
		//If we don't have sound player then we can't play the sound
		if( this.soundPlayer == null)
			return false;
		
		//Check the state type if its equal to the current one we cant do it as its already in that state
		return ( this.soundPlayer.getStatus() == actionState )? false:true; 
	}
	
	//----------------------------------
	// Overridden / Implemented
	//----------------------------------

	@SuppressWarnings("rawtypes")
	@Override
	public void opened(Object arg0, Map arg1) {}

	@SuppressWarnings("rawtypes")
	@Override
	public void progress(int arg0, long arg1, byte[] arg2, Map arg3) {}

	@Override
	public void setController(BasicController arg0) {}

	@Override
	/**
	 * Process state changes so we can act accordingly
	 */
	public void stateUpdated(BasicPlayerEvent event) {
		
		//If the event an end of media event
		if( event.getCode() == BasicPlayerEvent.EOM ) {
			
			//If we are looping this track then call the play function again 
			if( this.loopTrack )
			{
				this.stop();
				this.play();
			}
		}
	}
	
}
